/*
 * Copyright (c) 2006-2011, Petr Panteleyev
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 *    3. The name of the author may not be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.panteleyev.livejournal;

import java.awt.BorderLayout;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Date;

import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.border.BevelBorder;
import javax.swing.border.SoftBevelBorder;
import javax.swing.table.AbstractTableModel;

import org.panteleyev.blogclient.BloggerHistoryFrame;
import org.panteleyev.blogclient.DatePicker;
import org.panteleyev.blogclient.Icon;
import org.panteleyev.blogclient.MainClass;
import org.panteleyev.blogclient.Options;
import org.panteleyev.blogclient.events.PostDeletedEvent;
import org.panteleyev.livejournal.events.CommentsUpdatedEvent;
import org.panteleyev.ljapi.LJAccount;
import org.panteleyev.ljapi.LJComment;
import org.panteleyev.ljapi.LJCommentCacheInfo;
import org.panteleyev.ljapi.LJCommentMetaDataUpdate;
import org.panteleyev.ljapi.LJEvent;
import org.panteleyev.ljapi.LJException;
import org.panteleyev.swing.OSUtil;
import org.panteleyev.swing.SwingFactory;
import org.panteleyev.swing.SwingWorkerEx;
import org.panteleyev.swing.TableUtil;
import org.panteleyev.swing.events.UpdateFrameEvent;

/**
 *
 * @author  Petr Panteleyev
 */
public class LJHistoryFrame extends BloggerHistoryFrame<LJUserProfile,LJEvent> {
    private DatePicker          datePicker = new DatePicker();
    private JComboBox           sharedJournalsComboBox;
    private JComboBox<String>   lastComboBox;
    private JCheckBox           beforeCheck;

    private class ChangeAttrThread extends Thread {
        private ChangeAttributesDialog m_dial;

        public ChangeAttrThread(ChangeAttributesDialog dial) {
            m_dial = dial;
        }

        public ChangeAttributesDialog getDialog() {
            return m_dial;
        }
    }

    private class HistoryUpdateThread extends SwingWorkerEx<Object,Object> {

        HistoryUpdateThread(Window comp) {
            super(comp, getBusyLabel());
            enableActions(false);
        }

        @Override
        protected Object doInBackground() throws Exception {
            try {
                try {
                    m_lastN = Integer.parseInt((String)lastComboBox.getSelectedItem());
                } catch (NumberFormatException e) {
                    m_lastN = LASTN_DEFAULT;
                }

                m_lastN = Math.min(m_lastN, LASTN_MAX);
                if (m_lastN <= 0) {
                    m_lastN = LASTN_DEFAULT;
                }

                Date beforeDate = null;
                if (beforeCheck.isSelected()) {
                    beforeDate = datePicker.getDate();
                }

                _useJournal = (String)sharedJournalsComboBox.getSelectedItem();

                setHistory(_account.loadHistory(EVENT_TRUNC, m_lastN, beforeDate, _useJournal));
            } catch (IOException | LJException e) {
                setException(e);
            }

            return null;
        }

        @Override
        protected void done() {
            if (getException() == null) {
                getTableModel().fireTableDataChanged();
                TableUtil.adjustTableColumnSizes(getTable(), 2, getScroller());
            } else {
                handleException(getException());
            }

            enableActions(true);
            super.done();
        }
    }


    public static final int EVENT_TRUNC     = 70;
    public static final int LASTN_DEFAULT   = 20;
    public static final int LASTN_MAX       = 50;

    /*
     * Actions
     */
    AbstractAction              viewCommentsAction;
    AbstractAction              changeAttrAction;
    AbstractAction              updateCommentsAction;

    private LJAccount           _account;
    private int                 m_lastN;
    private String              _useJournal;

    private final String[] historyTableHeaderNames = {
        " ",
        getTableDateColumnHeader(),
        getTableSubjectColumnHeader(),
    };

    public LJHistoryFrame(LJUserProfile profile) {
        super(profile);

        m_lastN = LASTN_DEFAULT;

        _account = profile.getAccount();
        initComponents();
        createMenuBar();
        createPopupMenu();
        createHistoryTable();

        receive(new UpdateFrameEvent(this));

        Options.getBounds(this);
    }

    private void initComponents() {
        getContentPane().setLayout(new BorderLayout());

        sharedJournalsComboBox = new JComboBox<>(getAccount().getSharedJournals().toArray());
        lastComboBox = new JComboBox<>(new String[] {"10", "20", "30", "40", "50"});
        beforeCheck = new JCheckBox(new AbstractAction(getBeforeCheckBoxLabel()) {
            @Override
            public void actionPerformed(ActionEvent e) {
            }
        });

        getContentPane().add(
            SwingFactory.createPanel(new SoftBevelBorder(BevelBorder.RAISED),
                SwingFactory.LAYOUT.BOX_X,
                sharedJournalsComboBox,
                Box.createHorizontalStrut(5),
                new JLabel(getLastLabel()),
                Box.createHorizontalStrut(3),
                lastComboBox,
                Box.createHorizontalStrut(5),
                beforeCheck,
                Box.createHorizontalStrut(3),
                datePicker,
                Box.createHorizontalGlue(),
                getBusyLabel()
            ), BorderLayout.NORTH
        );

        getContentPane().add(getScroller(), BorderLayout.CENTER);

        SwingFactory.setWidthMaximumToPreferred(sharedJournalsComboBox, lastComboBox);
        SwingFactory.setHeightMaximumToPreferred(sharedJournalsComboBox, lastComboBox);

        // Mac OS X properties
        if (OSUtil.isMacOSX()) {
            lastComboBox.putClientProperty("JComboBox.isPopDown", Boolean.TRUE);
            sharedJournalsComboBox.putClientProperty("JComboBox.isPopDown", Boolean.TRUE);
        }
    }

    private LJAccount getAccount() { return _account; }

    @Override
    public void update() {
        new HistoryUpdateThread(this).execute();
    }

    @Override
    protected void createActions() {
        super.createActions();

        viewCommentsAction = new ViewCommentsAction() {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                onViewComments();
            }
        };
        addAction(viewCommentsAction);

        changeAttrAction  = new AbstractAction(L10N.HISTORY_FRAME_CHANGE_ATTRIBUTES_ACTION.s(), Icon.EMPTY.i()) {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                onChangeAttributes();
            }
        };
        addAction(changeAttrAction);

        updateCommentsAction = new AbstractAction(L10N.HISTORY_FRAME_UPDATE_COMMENTS_ACTION.s(), Icon.EMPTY.i()) {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                onUpdateComments();
            }
        };
        addAction(updateCommentsAction);
    }

    private void createMenuBar() {
        createMenuBar(
            createFileMenu(),
            createMenu(getHistoryMenuName(), null,
                getEditEventAction(),
                getDeleteEventAction(),
                null,
                viewCommentsAction,
                updateCommentsAction,
                null,
                changeAttrAction,
                getExportEventsAction()
            ),
            createProfileMenu(),
            createToolsMenu(),
            createWindowMenu(),
            createHelpMenu()
        );
    }

    @Override
    protected void enableActions(boolean enable) {
        super.enableActions(enable);

        /* Forbid comments for non-main journal */
        int jIndex = sharedJournalsComboBox.getSelectedIndex();
        viewCommentsAction.setEnabled(enable && (jIndex == 0));
        updateCommentsAction.setEnabled(enable && (jIndex == 0));
    }

    private void createPopupMenu() {
        getScroller().setComponentPopupMenu(createMenu(null, null,
            getEditEventAction(),
            getDeleteEventAction(),
            null,
            viewCommentsAction,
            updateCommentsAction,
            null,
            changeAttrAction,
            getExportEventsAction()).getPopupMenu());
    }

    private void onViewComments() {
        int row = getTable().getSelectedRow();
        if (row != -1) {
            LJEvent event = getHistory().get(row);
            if (event != null) {
                getProfile().openCommentsWindow(event);
            }
        }
    }

    private void onUpdateComments() {
        Thread thread = new Thread() {
            @Override
            public void run() {
                try {
                    SwingUtilities.invokeAndWait(new Runnable() {
                        @Override
                        public void run() {
                            enableActions(false);
                            getBusyLabel().setBusy(true);
                        }
                    });

                    String ljSession = getAccount().sessionGenerate(false, false);
                    getAccount().getCommentsMetaData(ljSession);

                    ArrayList<LJComment> comments = new ArrayList<>();
                    getAccount().getCommentsData(ljSession, comments);

                    getProfile().saveCommentsMetaData(getAccount().getCommentsMeta());
                    getProfile().saveUserMap(getAccount().getUserMap());
                    getProfile().storeComments(comments, getAccount().getCommentsInfo());

                    getProfile().sendApplicationEvent(new CommentsUpdatedEvent(this));
                } catch (InterruptedException | InvocationTargetException | IOException | LJException e) {
                    handleException(e);
                } finally {
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            getBusyLabel().setBusy(false);
                            enableActions(true);
                        }
                    });
                }
            }
        };

        if (getProfile().getCommentsMetaInfoUpdate() == LJCommentMetaDataUpdate.ON_UPDATE) {
            LJCommentCacheInfo commInfo = getAccount().getCommentsInfo();
            commInfo.resetMaxID();
            getAccount().setCommentsInfo(commInfo);
        }

        thread.start();
    }

    private void onChangeAttributes() {
        final int[] rows = getTable().getSelectedRows();
        if (rows.length == 0) {
            return;
        }

        ChangeAttributesDialog dial = new ChangeAttributesDialog(this, _account);
        if (dial.showDialog() && dial.changeAnything()) {
            Thread thread = new ChangeAttrThread(dial) {
                @Override
                public void run() {
                    try {
                        SwingUtilities.invokeAndWait(new Runnable() {
                            @Override
                            public void run() {
                                enableActions(false);
                                getBusyLabel().setBusy(true);
                            }
                        });


                        /* Prepare data */
                        String uPic = null;
                        if (getDialog().changeUserpic()) {
                            int selIndex = getDialog().getUserpicIndex();
                            if (selIndex > 0) {
                                uPic = _account.getUserpicKeys().get(selIndex);
                            }
                        }

                        int moodID = -1;
                        String moodText = null;
                        if (getDialog().changeMood()) {
                            int moodIndex = getDialog().getMoodIndex();
                            moodText = getDialog().getMoodString();
                            if (moodIndex != -1) {
                                if (moodIndex != -1) {
                                    moodID = _account.getMoodID(moodIndex);
                                }
                            }
                        }

                        for (int row : rows) {
                            LJEvent event = getHistory().get(row);

                            if ((event.getPoster() == null) || (event.getPoster().equals(getProfile().getUserName()))) {
                                LJEvent fullEvent = _account.loadEvent(event.getId(), event.getJournal());

                                if (getDialog().changeAccessLevel()) {
                                    fullEvent.setAccessLevel(getDialog().getAccessLevel(), getDialog().getGroupMask());
                                }

                                if (getDialog().changeLocation()) {
                                    fullEvent.setLocation(getDialog().getLocationString());
                                }

                                if (getDialog().changeMusic()) {
                                    fullEvent.setMusic(getDialog().getMusic());
                                }

                                if (getDialog().changeScreen()) {
                                    fullEvent.setScreeningType(getDialog().getScreeningType());
                                }

                                if (getDialog().changeMood()) {
                                    fullEvent.setMood(moodID, moodText);
                                }

                                if (getDialog().changeUserpic()) {
                                    fullEvent.setUserpic(uPic);
                                }

                                _account.postEvent(fullEvent);
                            }
                        }
                    } catch (InterruptedException | InvocationTargetException | IOException | LJException e) {
                    } finally {
                        SwingUtilities.invokeLater(new Runnable() {
                            @Override
                            public void run() {
                                getBusyLabel().setBusy(false);
                                enableActions(true);
                            }
                        });
                    }
                }
            };

            thread.start();
        }
    }

    @Override
    protected LJEvent getFullEvent(LJEvent event) {
        try {
            return _account.loadEvent(event.getId(), event.getJournal());
        } catch (IOException | LJException ex) {
            return null;
        }
    }

    @Override
    protected void editEvent() {
        int index = getTable().getSelectedRow();
        if (index != -1) {
            LJEvent event = getHistory().get(index);
            if ((event.getPoster() == null) || (event.getPoster().equals(getProfile().getUserName()))) {
                new OpenPostThread(getProfile(), event.getId(), event.getJournal()).execute();
            }
        }
    }

    @Override
    protected void deleteEvent() {
        final int[] rows = getTable().getSelectedRows();

        if (rows.length == 0) {
            return;
        }

        if (!MainClass.showConfirmDialog(this)) {
            return;
        }

        Thread thread = new Thread() {
            @Override
            public void run() {
                final ArrayList<LJEvent> toDelete = new ArrayList<>(rows.length);

                try {
                    SwingUtilities.invokeAndWait(new Runnable() {
                        @Override
                        public void run() {
                            enableActions(false);
                            getBusyLabel().setBusy(true);
                        }
                    });


                    /* Delete events from account */
                    for (int index : rows) {
                        try {
                            LJEvent event = getHistory().get(index);
                            if ((event.getPoster() == null) || (event.getPoster().equals(getProfile().getUserName()))) {
                                _account.deleteEvent(event);
                                toDelete.add(event);
                            }
                        } catch (IOException | LJException e) {
                            handleException(e);
                        }
                    }

                    /* Remove successfully deleted events from history and close windows */
                    SwingUtilities.invokeAndWait(new Runnable() {
                        @Override
                        public void run() {
                            for (LJEvent event : toDelete) {
                                getProfile().sendApplicationEvent(new PostDeletedEvent(this, event));
                                getHistory().remove(event);
                            }
                            getTableModel().fireTableDataChanged();
                        }
                    });
                } catch (InterruptedException | InvocationTargetException e) {
                } finally {
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            getBusyLabel().setBusy(false);
                            enableActions(true);
                        }
                    });
                }
            }
        };

        thread.start();
    }

    private void createHistoryTable() {
        setTableModel(new AbstractTableModel() {
            @Override
            public int getColumnCount() {
                return historyTableHeaderNames.length;
            }

            @Override
            public int getRowCount() {
                return getHistory().size();
            }

            @Override
            public Object getValueAt(int row, int col) {
                return getHistory().get(row);
            }

            @Override
            public String getColumnName(int column) {
                return historyTableHeaderNames[column];
            }
        });

        getTable().setDefaultRenderer(Object.class, new HistoryTableCellRenderer(getProfile().getUserName()));
    }

}
